package com.jotta.client.communicate;

import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.TimeZone;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;

import android.net.Uri;
import com.jotta.client.data.*;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.jotta.client.setting.SettingManager;
import com.jotta.client.utility.Utility;

public class Parser {

    public static SimpleDateFormat ISO8601UTC = new SimpleDateFormat("yyyy-MM-dd-'T'HH:mm:SSS'Z'");

    public static Parser instance;
    private static final String XML_FOLDER_PARENT_NODE_NAME = "folders";
    private static final String XML_FOLDER_NODE_NAME = "folder";
    private static final String XML_FILE_NODE_NAME = "file";
    private static final String XML_MP_NAME_NODE_NAME = "name";
    private static final String XML_MP_DELETED_NODE_NAME = "deleted";
    private static final String XML_DEVICE_PAGE_MP_NODE_NAME = "mountPoint";
    private DocumentBuilder builder;

    public static String TAG = "JOTTA_PARSER";

    public Parser() {
        super();
        try {
            builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            ISO8601UTC.setTimeZone(TimeZone.getTimeZone("UTC"));

        } catch (ParserConfigurationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (FactoryConfigurationError e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * Skeleton method
     */
    public static Parser getInstance() {
        if (instance == null) instance = new Parser();
        return instance;
    }

    public ArrayList<PictureInfo> getPictureInfos(Document doc, String currentPath) throws IOException, SAXException {

        ArrayList<PictureInfo> rows = new ArrayList<PictureInfo>();


        NodeList files = doc.getElementsByTagName(XML_FILE_NODE_NAME);

        for (int i = 0; i < files.getLength(); i++) {
            Node file = files.item(i);
            FileInfo row = getInfoFromFileNode(file, currentPath);
            if (row.getTypeOfFile() == TypeOfFile.PICTURE && row.getDeleted() == null &&
                    row.getState().equals(FileState.COMPLETED)) {
                rows.add(new PictureInfo(row));
            }
        }
        return rows;

    }

    /*  static String prettyPrintWithTrAX(Node document) throws TransformerException {
        // Pretty-prints a DOM document to XML using TrAX.
        // Note that a stylesheet is needed to make formatting reliable.
        //  TransformerFactory transformerFactory = TransformerFactory.newInstance();
        //Transformer transformer = transformerFactory.newTransformer(new StreamSource("pretty-print.xsl"));
        Transformer xformer = TransformerFactory.newInstance().newTransformer();

        StringWriter stringWriter = new StringWriter();
        StreamResult streamResult = new StreamResult(stringWriter);
        DOMSource domSource = new DOMSource(document);
        xformer.transform(domSource, streamResult);

        return stringWriter.toString();
    }*/

    public Document parseInputStream(InputStream inputStream) throws IOException, SAXException {
        return builder.parse(inputStream);

    }

    public ArrayList<ItemInfo> parseXmlData(Document doc, JottaPath jottaPath) throws SAXException, IOException {

        String rootNodeName = doc.getDocumentElement().getNodeName();

        if (rootNodeName.equals("user"))
            return getDataFromHomePage(doc);
        else if (rootNodeName.equals("device"))
            return getDataFromDevicePage(doc);
        else
            return getDataFromFolderPage(jottaPath, doc);


    }

    /**
     * Parse xml file represent user's homepage and convert it to list of item
     * info
     *
     * @return List of Item info
     */
    private ArrayList<ItemInfo> getDataFromHomePage(Document doc)
            throws SAXException {
        ArrayList<ItemInfo> rows = new ArrayList<ItemInfo>();
        Node capacity = doc.getElementsByTagName("capacity").item(0);
        SettingManager.getInstance()
                .setServerCapacity(
                        Long.parseLong(capacity.getChildNodes().item(0)
                                .getNodeValue()));

        Node usage = doc.getElementsByTagName("usage").item(0);
        SettingManager.getInstance().setServerUsage(
                Long.parseLong(usage.getChildNodes().item(0).getNodeValue()));
        DeviceInfo archiveDevice = new DeviceInfo();
        NodeList devices = doc.getElementsByTagName("device");
        for (int i = 0; i < devices.getLength(); i++) {
            Node device = devices.item(i);
            DeviceInfo row = getInfoFromDeviceNode(device);
            if (row.getTypeOfDevice() == TypeOfDevice.JOTTA)
                archiveDevice = row;
            else
                rows.add(row);
        }
        if (archiveDevice != null && archiveDevice.getTitle() != "")
            rows.add(archiveDevice);

        return rows;
    }

    /**
     * Parse xml file represent user's mount point page and convert it to list
     * of item info
     *
     * @return List of Item info
     */
    private ArrayList<ItemInfo> getDataFromDevicePage(Document doc) throws SAXException {
        ArrayList<ItemInfo> rows = new ArrayList<ItemInfo>();
        NodeList mountPoints = doc.getElementsByTagName(XML_DEVICE_PAGE_MP_NODE_NAME);
        for (int i = 0; i < mountPoints.getLength(); i++) {
            Node mountPoint = mountPoints.item(i);
            ItemInfo row = getInfoFromMountPointNode(mountPoint);
            if (row != null) {
                rows.add(row);
            }
        }
        return rows;
    }

    /**
     * Parse xml file represent user's folder page and convert it to list of
     * item info
     * @param jottaPath path to get
     * @param doc xml source
     * @return list of items
     * @throws org.xml.sax.SAXException throws when xml parsing fails
     */
    private ArrayList<ItemInfo> getDataFromFolderPage(JottaPath jottaPath, Document doc) throws SAXException {

        ArrayList<ItemInfo> rows = new ArrayList<ItemInfo>();

        NodeList folders = doc.getElementsByTagName(XML_FOLDER_NODE_NAME);
        for (int i = 0; i < folders.getLength(); i++) {
            Node folder = folders.item(i);
            Node parent = folder.getParentNode();
            String parentName = parent.getNodeName();
            if (parentName.equals(XML_FOLDER_PARENT_NODE_NAME)) {
                ItemInfo row = getInfoFromFolderNode(folder);
                if (row != null) {
                    rows.add(row);
                }
            }
        }

        NodeList files = doc.getElementsByTagName(XML_FILE_NODE_NAME);
        for (int i = 0; i < files.getLength(); i++) {
            Node file = files.item(i);
            ItemInfo row = getInfoFromFileNode(file, jottaPath.getFullPathEncoded());
            if (row != null && row instanceof FileInfo) {
                FileInfo fileInfo = (FileInfo) row;
                if (fileInfo.getState().equals(FileState.COMPLETED) && fileInfo.getDeleted() == null) {
                    rows.add(row);
                }
            } else {
                rows.add(row);
            }
        }


        return rows;
    }

    /**
     * Parse file node date and return ItemInfo object
     */
    private FileInfo getInfoFromFileNode(Node file, String currentPathEncoded) {

        //Log.i(TAG, "!!!getInfoFromFileNode>currentPathEncoded:" + currentPathEncoded);

        FileInfo fileInfo = new FileInfo();

        String fileName = "";
        String deletedTime = "";

        /* get name of file */
        NamedNodeMap attributes = file.getAttributes();
        for (int index = 0; index < attributes.getLength(); index++) {
            Attr attribute = (Attr) attributes.item(index);

            if (attribute.getName().equals(XML_MP_NAME_NODE_NAME)) {
                fileName = attribute.getValue();
                fileInfo.setTitle(fileName);
            }
            if (attribute.getName().equals(XML_MP_DELETED_NODE_NAME)) {
                deletedTime = attribute.getValue();
                try {
                    Date deletedDate = ISO8601UTC.parse(deletedTime);
                    fileInfo.setDeleted(deletedDate);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }

        }

        /* get size of file */
        NodeList fileInfos = file.getChildNodes();
        for (int i = 0; i < fileInfos.getLength(); i++) {
            Node fileInfoNode = fileInfos.item(i);

            if (fileInfoNode.getNodeName().equals("currentRevision")) {
                NodeList revisionInfos = fileInfoNode.getChildNodes();

                for (int j = 0; j < revisionInfos.getLength(); j++) {
                    Node revisionInfo = revisionInfos.item(j);

                    if (revisionInfo.getNodeName().equals("size")) {
                        long actualSize = Long.parseLong(revisionInfo
                                .getChildNodes().item(0).getNodeValue());
                        fileInfo.setFileSize(actualSize);

                    } else if (revisionInfo.getNodeName().equals("number")) {
                        int revisionNumber = Integer.parseInt(revisionInfo
                                .getChildNodes().item(0).getNodeValue());
                        fileInfo.setCurrentRevionsNumber(revisionNumber);

                    } else if (revisionInfo.getNodeName().equals("state")) {
                        String fileState = revisionInfo.getChildNodes().item(0).getNodeValue();
                        fileInfo.setState(FileState.valueOf(fileState));

                    } else if (revisionInfo.getNodeName().equals("mime")) {
                        String mimeType = revisionInfo.getChildNodes().item(0)
                                .getNodeValue();
                        fileInfo.setMimeType(mimeType);
                    }
                }

            }
        }


        fileInfo.setTypeOfFile(Utility.filterFile(fileName));

        fileInfo.setPathOfParentFolder(currentPathEncoded);
        fileInfo.setPathOfItem(currentPathEncoded + Uri.encode(fileName));

        return fileInfo;
    }

    /**
     * Parse folder node date and return ItemInfo object, if folder node is root
     * node this function return null
     *
     */
    private ItemInfo getInfoFromFolderNode(Node folder) {


        FolderInfo folderInfo = new FolderInfo();
        NamedNodeMap attributes = folder.getAttributes();

        for (int index = 0; index < attributes.getLength(); index++) {
            Attr attribute = (Attr) attributes.item(index);

            if (attribute.getName().equals(XML_MP_NAME_NODE_NAME)) {
                folderInfo.setTitle(attribute.getValue());
            }

            if (attribute.getName().equals(XML_MP_DELETED_NODE_NAME)) {
                return null;
            }
        }

        folderInfo.setType(TypeOfItem.FOLDER);
        return folderInfo;
    }

    /**
     * Parse Device node date and return ItemInfo object
     *
     */
    private DeviceInfo getInfoFromDeviceNode(Node device) {
        DeviceInfo deviceInfo = new DeviceInfo();
        NodeList deviceInfoNode = device.getChildNodes();
        String deviceName = "";
        String deviceType = "";
        for (int index = 0; index < deviceInfoNode.getLength(); index++) {
            Node node = deviceInfoNode.item(index);
            if (node.getNodeName().equals(XML_MP_NAME_NODE_NAME)) {
                deviceName = node.getChildNodes().item(0).getNodeValue();
            } else if (node.getNodeName().equals("type")) {
                deviceType = node.getChildNodes().item(0).getNodeValue();
            }
        }
        if (deviceName.equals(Configuration.JOTTA_DEVICE)
                && deviceType.equals(Configuration.JOTTA_DEVICE_TYPE)) {
            deviceInfo.setTitle(Configuration.ARCHIVE_NAME);
            deviceInfo.setTypeOfDevice(TypeOfDevice.JOTTA);
        } else {
            deviceInfo.setTitle(deviceName);
            deviceInfo.setTypeOfDevice(deviceType);
        }
        return deviceInfo;
    }

    /**
     * Parse mount point node date and return ItemInfo object
     *
     */
    private ItemInfo getInfoFromMountPointNode(Node mountPoint) {
        MountPointInfo mountPointInfo = new MountPointInfo();
        NodeList mountPointInfoNode = mountPoint.getChildNodes();
        for (int index = 0; index < mountPointInfoNode.getLength(); index++) {
            Node node = mountPointInfoNode.item(index);

            if (node.getNodeName().equals(XML_MP_NAME_NODE_NAME)) {
                mountPointInfo.setTitle(node.getChildNodes().item(0).getNodeValue());
            }
            if (node.getNodeName().equals(XML_MP_DELETED_NODE_NAME)) {
                return null;
            }
        }

        mountPointInfo.setType(TypeOfItem.MOUNTPOINT);
        return mountPointInfo;
    }
}
